package glassline;

import glassline.Glass.WorkStation;
import glassline.interfaces.Conveyor;
import glassline.interfaces.Popup;
import glassline.interfaces.Sensor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import transducer.TChannel;
import transducer.TEvent;
import transducer.Transducer;
import agent.Agent;

public class ConveyorAgent extends Agent implements Conveyor {
	Transducer transducer;
	/**
	 * Identification of conveyor, used to match it up with corresponding GUI
	 */
	Object[] conveyorNumber = new Object[1];
	public boolean on = true;
	/**
	 * True if there is a begninning popup and its state was changed
	 */
	boolean beginningPopupChanged = false;
	/**
	 * True if a message informing the previous conveyor that I am stopped with glass blocking the entry has been sent
	 */
	boolean sentAlertGlassBlockingEntry = false;
	
	enum BeginningPopupState
			{ noBeginningPopup, popupUp, popupDown};
	enum ConveyorEntryState
			{ moving, stoppedAndBlocked, previousConveyor};
	class MyConveyor {
		Conveyor conveyor;		
		BeginningPopupState popupState;	
		ConveyorEntryState blockState;
		
		public MyConveyor(Conveyor c, boolean prevConveyor) {
			conveyor = c;
			if (prevConveyor) {
				popupState = BeginningPopupState.noBeginningPopup;
				blockState = ConveyorEntryState.previousConveyor;
			} else {
				if (c.hasBeginningPopup()) popupState = BeginningPopupState.popupDown;
				else popupState = BeginningPopupState.noBeginningPopup;
				blockState = ConveyorEntryState.moving;
			}
		}	
	}
	MyConveyor previousConveyor;
	MyConveyor nextConveyor;
	
	/**
	 * state of popup. Locked if there is glass over it that does not need to be transported by the popup,
	 * so it must stay down while the glass passes over it
	 */
	public enum PopupState
			{up, down, locked};
	/**
	 * Functional location of popup on conveyor.
	 * beginning: at the very start of the conveyor
	 * middle: in between the beginning of the conveyor and end of conveyor
	 * end: at the very end of the conveyor
	 *
	 */
	public enum PopupLocation
			{beginning, middle, end};
	public class MyPopup {
		Popup popup;
		WorkStation workType; // type of operation on glass popup takes a piece of glass to		
		public PopupState state = PopupState.down;
		public boolean allWorkstationsFull = false;	
		PopupLocation location;
		
		public MyPopup(Popup p, WorkStation type, PopupLocation l) {
			popup = p;
			workType = type;
			location = l;
		}
	}
	public List<MyPopup> popups = new ArrayList<MyPopup>(); //popups on conveyor in order that they are in
	
	/**
	 * tells Conveyor what purpose the sensor serves.
	 * Transit is simply not a prePopup or popup sensor
	 */
	public enum SensorType
			{transit, prePopup, popup};
	public class MySensor {
		
		public Sensor sensor;
		SensorType type;
		MyPopup associatedPopup; //null if the type is transit, otherwise represents the popup the sensor is on or comes immediately before
		boolean isConveyorEnd; //true if it's the last sensor on the conveyor
		
		public MySensor(Sensor s, SensorType t, Popup popup, boolean conveyorEnd) {
			sensor = s;
			type = t;
			if (popup == null) {
				associatedPopup = null;
			} else {
				for (MyPopup p: popups) {
					if (p.popup == popup) {
						associatedPopup = p;
						break;
					}
				}
			}
			isConveyorEnd = conveyorEnd;
			
		}
	}
	public List<MySensor> sensors = new ArrayList<MySensor>(); //sensors on conveyor in order, first sensor closest to end of conveyor
	
	public enum GlassState
			{ onConveyor, waitingToGoToPopup, withPopup, waitingToLeaveConveyor};
	public class MyGlass {
		public Glass glass;
		/**
		 * this is the last sensor that detected this piece of glass
		 */
		public MySensor location = null;

		public GlassState state = GlassState.onConveyor;
		
		public MyGlass(Glass g) {
			glass = g;
		}
	}
	public List<MyGlass> glass = Collections.synchronizedList(new ArrayList<MyGlass>()); //glass on conveyor in order that it is physically in, with first glass being closest to end of conveyor
	
	//initialization, must be done in this order
	/**
	 * Creates a ConveyorAgent with transducer t and conveyorNumber whichConveyor
	 * This does not add popups, sensors, or other conveyors to this conveyor.
	 * To add those, use the methods below IN THE ORDER THEY ARE PRESENTED
	 * @param t transducer conveyor transmits on
	 * @param whichConveyor used to match up this conveyor with its gui counterpart
	 */
	public ConveyorAgent(Transducer t, int whichConveyor) {
		transducer = t;
		conveyorNumber[0] = whichConveyor;
	}
	/**
	 * Adds a popup that is on this conveyor.
	 * The first popup you add is closest to the end of the conveyor!
	 * If you think of the conveyor as moving from left to right,
	 * each subsequent popup goes to the left of the last popup you added.
	 * @param p popup to add
	 * @param type type of workstations this popup can take glass to
	 * @param location what functional part of the conveyor the popup is in
	 */
	public void addPopup(Popup p, WorkStation type, PopupLocation location) {
		popups.add(new MyPopup(p, type, location));
	}
	/**
	 * Adds a sensor that is on this conveyor.
	 * The first sensor you add is closest to the end of the conveyor!
	 * If you think of the conveyor as moving from left to right,
	 * each subsequent sensor goes to the left of the last popup you added.
	 * @param s sensor to be added
	 * @param t indicates if this center before a popup, on a popup, or neither
	 * @param popup the popup that this sensor is associated with. if none, than this is just null
	 * @param ConveyorEnd true if this is the last sensor on the conveyor (and thus the first one you should add!), false otherwise
	 */
	public void addSensor(Sensor s, SensorType t, Popup popup, boolean ConveyorEnd) {
		sensors.add(new MySensor(s, t, popup, ConveyorEnd));
	}
	/**
	 * Adds the given conveyor as the previous or next conveyor in relation to this conveyor.
	 * @param c the conveyor to be added
	 * @param isPreviousConveyor true if c comes before this conveyor in the glass line, false otherwise
	 */
	public void introduceConveyor(Conveyor c, boolean isPreviousConveyor) {
		if (isPreviousConveyor) previousConveyor = new MyConveyor(c, isPreviousConveyor);
		else nextConveyor = new MyConveyor(c, isPreviousConveyor);
	}	
	
	//messages
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgHereIsGlass(glassline.Glass)
	 */
	@Override
	public void msgHereIsGlass(Glass g) {
		glass.add(new MyGlass(g));
		stateChanged();
	}
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgSensedGlass(glassline.Sensor)
	 */
	@Override
	public void msgSensedGlass(Sensor sensor) {
		MySensor sender = null;
		for (MySensor s: sensors) {
			if (s.sensor == sensor) {
				sender = s;
				break;
			}
		}
		MySensor previousSensor = null;
		int previousSensorIndex = sensors.indexOf(sender) + 1;
		if (previousSensorIndex < sensors.size()) {
			previousSensor = sensors.get(previousSensorIndex);
		}
		MyGlass part = null;
		synchronized (glass) {
			for (int i = 0; i < glass.size(); i++) { //the first glass with following conditions
				if (glass.get(i).location == previousSensor
						&& glass.get(i).state == GlassState.onConveyor) {
					part = glass.get(i);
					break;
				}
			}
		}
		part.location = sender;
		//TODO: handle where if 2 glasses are at workstation, but the one that got there second
		//makes it to the conveyor first, swap the order
		if (sender.isConveyorEnd && sender.type != SensorType.popup) {
			part.state = GlassState.waitingToLeaveConveyor;
		}
		if (sender.type == SensorType.popup) {
			for (WorkStation thing: part.glass.recipe.workstations.keySet()) {
				if (thing == sender.associatedPopup.workType) {
					part.state = GlassState.waitingToGoToPopup;
				}
			}
		}
		if (part.state != GlassState.waitingToGoToPopup) {
			int nextGlassIndex = glass.indexOf(part) - 1;
			if (nextGlassIndex >= 0) {
				MyGlass nextGlass = glass.get(nextGlassIndex);
				if (sender.type == SensorType.popup && nextGlass.state == GlassState.withPopup) {
					if (nextGlass.location == sender) {
						//swap the glass
						Do("Glass on conveyor passing glass in workstation");
						glass.add(nextGlassIndex, part); //keeps glass in correct order
						glass.remove(nextGlassIndex + 1);
						glass.add(nextGlassIndex + 1, nextGlass);
						glass.remove(nextGlassIndex + 2);
					}
				}
			}
		}
		stateChanged();
	}
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgIAmUp(boolean, glassline.PopupAgent)
	 */
	@Override
	public void msgIAmUp(boolean workstationsFull, Popup popup) {
		for (MyPopup p: popups) {
			if (p.popup == popup) {
				p.state = PopupState.up;
				p.allWorkstationsFull = workstationsFull;
				if (p.location == PopupLocation.beginning) {
					beginningPopupChanged = true;
				}
				stateChanged();
				return;
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgIAmDown(glassline.PopupAgent)
	 */
	@Override
	public void msgIAmDown(Popup popup) {
		for (MyPopup p: popups)
			if (p.popup == popup) {
				p.state = PopupState.down;
				if (p.location == PopupLocation.beginning) {
					beginningPopupChanged = true;
				}
				stateChanged();
				return;
			}
	}
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgIAmUp(glassline.PopupAgent)
	 */
	@Override
	public void msgIAmUp(Popup popup) {
		for (MyPopup p: popups) {
			if (p.popup == popup) {
				p.state = PopupState.up;
				if (p.location == PopupLocation.beginning) {
					beginningPopupChanged = true;
				}
				stateChanged();
				return;
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgHereIsGlass(glassline.Glass, glassline.PopupAgent)
	 */
	@Override
	public void msgHereIsGlass(Glass g, Popup popup) {
		MyPopup pop = null;
		for (MyPopup p: popups) {
			if (p.popup == popup) {
				p.state = PopupState.down;
				if (p.location == PopupLocation.beginning) {
					beginningPopupChanged = true;
				}
				p.allWorkstationsFull = false;
				pop = p;
				break;
			}
		}
		synchronized (glass) {
			for (MyGlass part : glass) {
				if (part.glass == g) {
					if (pop.location == PopupLocation.end)
						part.state = GlassState.waitingToLeaveConveyor;
					else
						part.state = GlassState.onConveyor;
					stateChanged();
					return;
				}
			}
		}
	}

	//scheduler
	public boolean pickAndExecuteAnAction() {
		if (on) {
			//highest priority: if glass is stopped before a popup that is up, stop the conveyor
			synchronized (glass) {
				for (MyGlass g : glass) {
					if (g.location != null) {
						if (g.location.type == SensorType.prePopup) {
							MyPopup upcomingPopup = g.location.associatedPopup;
							if (upcomingPopup.state == PopupState.up) {
								stopConveyor();
								return true;
							}
						}
					}
				}
			}
		}
		if (on) {
			//if glass is wwaiting to go to a popup, send the glass to the popup
			synchronized (glass) {
				for (MyGlass g : glass) {
					if (g.state == GlassState.waitingToGoToPopup) {
						sendGlassToPopup(g);
						return true;
					}
				}
			}
			//if glass is waiting to leave the conveyor, send the glass to the next conveyor
			synchronized (glass) {
				for (MyGlass g : glass) {
					if (g.state == GlassState.waitingToLeaveConveyor) {
						passGlassToNextConveyor(g);
						return true;
					}
				}
			}
		}
		if (!on) {
			//when the conveyor is off, go to sleep if there continues to be glass stopped before a popup that is up
			synchronized (glass) {
				for (MyGlass g : glass) {
					if (g.location != null) {
						if (g.location.type == SensorType.prePopup) {
							MyPopup upcomingPopup = g.location.associatedPopup;
							if (upcomingPopup.state == PopupState.up) {
								return false;
							}
						}
					}
				}
			}
			//if there are no conditions that should keep the conveyor stopped, start the conveyor
			startConveyor();
			return true;
		}
		return false;
	}

	//Actions
	
	/** Send glass to the next conveyor*/
	private void passGlassToNextConveyor(MyGlass g) {
		Do("Passing glass to " + nextConveyor.conveyor);
		nextConveyor.conveyor.msgHereIsGlass(g.glass);
		glass.remove(g);
		stateChanged();
	}
	
	/** Send glass to the popup*/
	private void sendGlassToPopup(MyGlass g) {
		Do("Sending glass to " + g.location.associatedPopup.popup);
		g.state = GlassState.withPopup;
		g.location.associatedPopup.popup.msgHereIsGlass(g.glass, this);
		stateChanged();
	}
	
	/** Stop the conveyor, tell animation to stop as well*/
	private void stopConveyor() {
		on = false;
		Do("Stop Conveyor");
		transducer.fireEvent(TChannel.CONVEYOR, TEvent.StopConveyor, conveyorNumber);
		stateChanged();
	}
	
	/** Start the conveyor, tell the animation to start the conveyor as well*/
	private void startConveyor() {
		on = true;
		sentAlertGlassBlockingEntry = false;
		Do("Start conveyor");
		transducer.fireEvent(TChannel.CONVEYOR, TEvent.StartConveyor, conveyorNumber);
		//previousConveyor.conveyor.msgImMovingAgain();
		stateChanged();
	}
	
	//utlities
	/* (non-Javadoc)
	 * @see glassline.Conveyor#hasBeginningPopup()
	 */
	public boolean hasBeginningPopup() {
		for (MyPopup p: popups) {
			if (p.location == PopupLocation.beginning) return true;
		}
		return false;
	}
	
	public String toString() {
		return "Conveyor " + conveyorNumber[0];
	}
}
